package controllers.validation.jobs;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;

import org.hibernate.validator.util.privilegedactions.GetAnnotationParameter;

import com.sun.org.apache.regexp.internal.recompile;

import models.CNAverage;
import models.NodeConfig;
import models.SensorConfig;
import models.SensorData;
import models.UserConfig;
import controllers.validation.utils.MessageTextController;
import play.Logger;
import play.Play;
import play.jobs.Every;
import play.jobs.Job;
import util.Persistence;
import util.PlausabilityCalculator;
import ch.bfh.abch.rems.REMSMulticastController;
import ch.bfh.abch.rems.sender.tinkerforge.TFMulticastSender;
import ch.bfh.abcm.rems.gateway.GateWayMessageType;
import ch.bfh.abcm.rems.gateway.GatewayMessage;
import ch.bfh.abcm.rems.sensors.tinkerforge.TFSensorType;
import ch.bfh.abcm.rems.tinkerforge.TFNodeAverageMessage;

public class SensorValidationJob extends AbstratctValidationJob {

    private static final long DEFAULT_SENSOR_MAX_TIME_DIFF = 6 + 1000;
    private static final int DEFAULT_MAX_SENSOR_DIVERGENT = 3000;

    private List<SensorConfig> sensors;
    ArrayList<Integer> plausabilityList = new ArrayList<Integer>();

    public SensorValidationJob(String measurementpoint) {
        super(measurementpoint);
    }

    public void doJob() throws Exception {
        // JOB_START
        plausabilityList.clear();
        Logger.debug("Start SensorValidationJob on Measurementpoint: %s", measurementpoint);
        ArrayList<Integer> averageList = new ArrayList<Integer>();
        initialiseSensorListeSensorTypeLogPrefix();
        validationJobLogger.debug(logprefix + "Sensor_JOB_START [MP: " + measurementpoint + "]");

        // SENSOR_RESPONDING / SENSOR_NOT_RESPONDING
        for (SensorConfig sensor : sensors) {
//            SensorData lastMeasurement = SensorData.getLastMeasurement(sensor.getUid());
            SensorData lastMeasurement = Persistence.getLastMeasurement(sensor.getUid());
            if (lastMeasurement == null) {
                Logger.warn("No lastMeasurement found");
                return;
            }
            long maxTimeDiff = getUserPropertyLong(UserConfig.KEY_SENSOR_MAX_TIME_DIFF_MS, DEFAULT_SENSOR_MAX_TIME_DIFF);
            if (!isComponentResponding(lastMeasurement.getTimestamp(), maxTimeDiff, sensor.toString())) {
                String snr_msg = mtc.getSensorNotRespondingMesssage(sensor.getUid(), sensor.getMeasurementPointIP(),
                        sensorType);
                validationJobLogger.error(logprefix+snr_msg);
                sendGatewayMessage(snr_msg, GateWayMessageType.SYSTEM_ERROR, sensorType);
            } else {
                String sr_msg = mtc.getSensorRespondingMesssage(sensor.getUid(), sensor.getMeasurementPointIP(),
                        sensorType);
                validationJobLogger.info(logprefix+sr_msg);
                // TODO: Only the last five if they are all new enought...
                Integer i = medianLastFiveMeasurements(sensor.getUid());
                if (i != null) {
                    averageList.add(i);
                }else {
                    Logger.warn("Not enought Sensordatas... ");
                }
            }
        }
        // SENSOR_VALUES_IN_RANGE / SENSOR_VALUES_OUT_OF_RANGE
        if (averageList.size() > 0) {
            String maxdiffKey = sensorType + UserConfig.MAX_DIVERGENT_KEY_EXT;
            checkSensorDivergentIsInRange(averageList, maxdiffKey);
            double plausibility = PlausabilityCalculator.calcPlausibilityElementsInList(plausabilityList, sensors.size());
            calcAndSendNodeMedian(averageList, sensorType, plausibility);
        } else {
            validationJobLogger.error("NO Elements in averageList, dont send ClusterNodeAverage");
        }
    }

    private void initialiseSensorListeSensorTypeLogPrefix() {
        sensors = SensorConfig.getSensors(measurementpoint);
        if (sensors.size() >= 1) {
            sensorType = sensors.get(0).getSensorType();
            createLogprefix(sensorType + "");
        } else {
            Logger.error("No sensors found for Measurementpoint %s", measurementpoint);
        }
    }

    private Integer medianLastFiveMeasurements(String uid) {
        List<Integer> lastFiveSensorValues = Persistence.getLastFiveMeasurements(uid);
        Collections.sort(lastFiveSensorValues);
        int sizeOfList = lastFiveSensorValues.size();
        if (sizeOfList ==0) {
            return null;
        }
        if (sizeOfList>=2) {
            return lastFiveSensorValues.get(2);
        }else {
            return lastFiveSensorValues.get(sizeOfList-1);
        }
    }

    private void checkSensorDivergentIsInRange(ArrayList<Integer> averageEachSensor, String key) {
        String loggAllValues = logprefix;
        for (Integer integer : averageEachSensor) {
            loggAllValues += ">"+integer + "< ";
        }
        validationJobLogger.debug(loggAllValues);
        int maxValue = Collections.max(averageEachSensor);
        int minValue = Collections.min(averageEachSensor);
        int diff = maxValue - minValue;
        int maxdiff = getUserPropertyInt(key, DEFAULT_MAX_SENSOR_DIVERGENT);
        if (diff > maxdiff) {
            String sdoor_msg = mtc.getSensorDivergentOutOfRangeMessage(measurementpoint, sensorType, diff, maxdiff);
            validationJobLogger.error(sdoor_msg);
            GatewayMessage gatewayMessage = new GatewayMessage(GateWayMessageType.SYSTEM_ERROR, sdoor_msg, sensorType);
            sender.sendGateWayMessage(gatewayMessage);

            plausabilityList = generateplausabilityList(averageEachSensor);
            validationJobLogger.debug(logprefix+"PlausabilityList: " + plausabilityList);

        } else {
            plausabilityList = averageEachSensor;
            String sdir_msg = mtc.getSensorDivergentInRangeMessage(measurementpoint, sensorType, diff, maxdiff);
            validationJobLogger.info(logprefix+sdir_msg);
        }
    }

    private ArrayList<Integer> generateplausabilityList(ArrayList<Integer> averageEachSensor) {
        ArrayList<Integer> rv = new ArrayList<Integer>();
        String keyusermax = sensorType + UserConfig.MAX_VALUE_USER_KEY_EXT;
        String keyusermin = sensorType + UserConfig.MIN_VALUE_USER_KEY_EXT;
        Logger.debug(keyusermin + " " + keyusermax);
        int ucmin = getUserPropertyInt(keyusermin, 0);
        int ucmax = getUserPropertyInt(keyusermax, 1);
        for (Integer integer : averageEachSensor) {
            validationJobLogger.debug(logprefix + "IsPlausible min: " + ucmin + " max" + ucmax + " val: " + integer
                    + ">" + (integer > ucmin && integer < ucmax) + "<");
            if ((integer >= ucmin && integer <= ucmax)) {
                rv.add(integer);
            }
        }
        if (rv.size() == averageEachSensor.size()) {
            validationJobLogger.debug(logprefix+"SensorValues are divergent but possible...");
            // Just remove one value to reduce the plausibility...
            // Not very nice but very effective
            rv.remove(0);
        }
        return rv;

    }

    private void calcAndSendNodeMedian(ArrayList<Integer> averageList, TFSensorType sensorType, double plausability) {
        Collections.sort(averageList);
        TFNodeAverageMessage tfNodeAverage = null;
        switch (averageList.size()) {
        case 0:
            // It can't be 0 but check it anyway (safety first)
            // Set value to 0 because its
            tfNodeAverage = new TFNodeAverageMessage(0, NODE_IP, measurementpoint, sensorType, 0);
            break;
        case 1:
            tfNodeAverage = new TFNodeAverageMessage(averageList.get(0), NODE_IP, measurementpoint, sensorType,
                    plausability);
            break;
        case 2:
            // TODO: nimm den Durchschnitt
        case 3:
            tfNodeAverage = new TFNodeAverageMessage(averageList.get(1), NODE_IP, measurementpoint, sensorType,
                    plausability);
            break;
        case 4:
        case 5:
            tfNodeAverage = new TFNodeAverageMessage(averageList.get(2), NODE_IP, measurementpoint, sensorType,
                    plausability);
            break;
        default:
            Logger.warn("Too much elements in averageList: %s (expected max 5)- unse number 2", averageList.size());
            tfNodeAverage = new TFNodeAverageMessage(averageList.get(2), NODE_IP, measurementpoint, sensorType,
                    plausability);
            break;
        }
        validationJobLogger.debug(logprefix + "Send " + tfNodeAverage.toString());
        sender.sendTFNodeAverageMessage(tfNodeAverage);
    }
}